﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Interactivity;
using System.Threading;
using System.ComponentModel;

namespace DeepZoom.Behaviors
{
    public class DeepZoomTouch : Behavior<MultiScaleImage>
    {

        public static readonly DependencyProperty MinViewportWidthrProperty =
           DependencyProperty.Register("MinViewportWidth", typeof(double), typeof(DeepZoomTouch), new PropertyMetadata(null));
        public static readonly DependencyProperty MaxViewportWidthProperty =
           DependencyProperty.Register("MaxViewportWidth", typeof(double), typeof(DeepZoomTouch), new PropertyMetadata(null));

        int lastId1 = 0, lastId2 = 0;
        Point lastElementPoint1, lastElementPoint2;
        int lastTimestamp;
        double directionX, directionY;
        int timebase;
        int timeInterval = 50;
        double delta = 2;
        double unitX, unitY, deltaX, deltaY;
        public delegate void InvokeDelegate();

        double diff = 5.0;

        [Category("Misc")]
        [DefaultValue(0)]
        public double MinViewportWidth { get; set; }

        [Category("Misc")]
        [DefaultValue(0)]
        public double MaxViewportWidth {get; set; }


        Thread thread;
        AutoResetEvent autoEvent;

        public DeepZoomTouch()
        {
            MinViewportWidth = MaxViewportWidth = 0;
        }


        protected override void OnAttached()
        {
            base.OnAttached();
            Touch.FrameReported += new TouchFrameEventHandler(Touch_FrameReported);

            autoEvent = new AutoResetEvent(false);
            thread = new Thread(this.Motion);
            thread.Start(autoEvent);
        }

        

        protected override void OnDetaching()
        {
            base.OnDetaching();
            Touch.FrameReported -= new TouchFrameEventHandler(Touch_FrameReported);
        }
        
        void Touch_FrameReported(object sender, TouchFrameEventArgs e)
        {
            TouchPointCollection tpList = e.GetTouchPoints(AssociatedObject);
            if (tpList.Count == 1)
            {
                if (lastId2 != 0)
                {
                    lastId2 = 0;
                    DeepZoomBounds.CheckBounds(AssociatedObject);
                } 
                SingleTouch(e);
            }
            else
                MultiTouch(e);

        }

        private void SingleTouch(TouchFrameEventArgs e)
        {
            var tp = e.GetPrimaryTouchPoint(AssociatedObject);
            if (tp == null)
                return;

            switch (tp.Action)
            {
                case TouchAction.Down:
                    lastId1 = tp.TouchDevice.Id;
                    lastElementPoint1 = tp.Position;
                    lastTimestamp = e.Timestamp;
                    directionX = directionY = 0;
                    e.SuspendMousePromotionUntilTouchUp();
                    unitX = unitY = 0;
                    break;

                case TouchAction.Move:
                    if (lastId1 == tp.TouchDevice.Id)
                    {
                        if (!InError(lastElementPoint1, tp.Position))
                        {
                            double viewportWidth = AssociatedObject.ViewportWidth;
                            Point viewportOrigin = AssociatedObject.ViewportOrigin;
                            Point newViewportOrigin = new Point()
                            {
                                X = viewportOrigin.X +
                                    lastElementPoint1.X * viewportWidth / AssociatedObject.ActualWidth -
                                    tp.Position.X * viewportWidth / AssociatedObject.ActualWidth,
                                Y = viewportOrigin.Y +
                                    lastElementPoint1.Y * viewportWidth / AssociatedObject.ActualWidth -
                                    tp.Position.Y * viewportWidth / AssociatedObject.ActualWidth
                            };
                            AssociatedObject.UseSprings = false;
                            AssociatedObject.ViewportOrigin = newViewportOrigin;

                            directionX = tp.Position.X - lastElementPoint1.X;
                            directionY = tp.Position.Y - lastElementPoint1.Y;
                            timebase = e.Timestamp - lastTimestamp;

                            lastElementPoint1 = tp.Position;
                            lastTimestamp = e.Timestamp;
                        }
                    }
                    else
                    {
                        lastId1 = tp.TouchDevice.Id;
                        lastElementPoint1 = tp.Position;
                        lastTimestamp = e.Timestamp;
                    }
                    break;

                case TouchAction.Up:
                    if (lastId1 != 0)
                    {
                        lastId1 = 0;
                        if (directionX == 0 && directionY == 0)
                            return;
                        if (timebase == 0)
                            return;

                        unitX = directionX / timebase * timeInterval;
                        unitY = directionY / timebase * timeInterval;

                        double distance = Math.Sqrt(Math.Pow(unitX, 2) + Math.Pow(unitY, 2));

                        deltaX = delta * unitX / distance;
                        deltaY = delta * unitY / distance;

                        autoEvent.Set();
                    }
                    break;
            }
        }

        private void Motion(object data)
        {
            AutoResetEvent autoEvent = (AutoResetEvent)data;

            for (; ; )
            {
                autoEvent.WaitOne();
               // do
               // {
                    Thread.Sleep(timeInterval);
                    AssociatedObject.Dispatcher.BeginInvoke(new InvokeDelegate(MoveNext));
                //} while (Math.Sign(unitX) == Math.Sign(deltaX) && Math.Sign(unitY) == Math.Sign(deltaY));
            }
        }

        public void MoveNext()
        {
            double r = AssociatedObject.ViewportWidth / AssociatedObject.ActualWidth;
            Point origin = AssociatedObject.ViewportOrigin;
            AssociatedObject.UseSprings = false;
            AssociatedObject.ViewportOrigin = new Point()
            {
                X = origin.X - r * unitX,
                Y = origin.Y - r * unitY
            };
            if (DeepZoomBounds.CheckBounds(AssociatedObject))
                unitX = unitY = 0;
            else
            {
                unitX -= deltaX;
                unitY -= deltaY;
            }
            if ((Math.Sign(unitX) == Math.Sign(deltaX) && Math.Sign(unitY) == Math.Sign(deltaY)))
            {
                autoEvent.Set();
            }
        }



        

        private void MultiTouch(TouchFrameEventArgs e)
        {
            var tpList = e.GetTouchPoints(AssociatedObject);
            var tp1 = tpList[0];
            var tp2 = tpList[1];
            if (tp1.Action == TouchAction.Up || tp2.Action == TouchAction.Up)
            {
                lastId2 = 0;
                DeepZoomBounds.CheckBounds(AssociatedObject);
            }
            else if (tp1.TouchDevice.Id != lastId1 || tp2.TouchDevice.Id != lastId2)
            {
                // new id
                lastId1 = tp1.TouchDevice.Id;
                lastId2 = tp2.TouchDevice.Id;
                lastElementPoint1 = tp1.Position;
                lastElementPoint2 = tp2.Position;
             }
            else
            {
                if (!InError(lastElementPoint1, tp1.Position) || !InError(lastElementPoint2, tp2.Position))
                {
                    double lastViewportWidth = AssociatedObject.ViewportWidth;
                    Point lastViewportOrigin = AssociatedObject.ViewportOrigin;

                    double newViewportWidth = lastViewportWidth * Distance(lastElementPoint1, lastElementPoint2) / Distance(tp1.Position, tp2.Position);
                    if (MinViewportWidth != 0 && newViewportWidth < MinViewportWidth)
                        newViewportWidth = MinViewportWidth;
                    if (MaxViewportWidth != 0 && newViewportWidth > MaxViewportWidth)
                        newViewportWidth = MaxViewportWidth;
                    Point newViewportOrigin = new Point()
                    {
                        X = lastViewportOrigin.X +
                            (lastElementPoint1.X + lastElementPoint2.X) / 2 * lastViewportWidth / AssociatedObject.ActualWidth -
                            (tp1.Position.X + tp2.Position.X) / 2 * newViewportWidth / AssociatedObject.ActualWidth,
                        Y = lastViewportOrigin.Y +
                            (lastElementPoint1.Y + lastElementPoint2.Y) / 2 * lastViewportWidth / AssociatedObject.ActualWidth -
                            (tp1.Position.Y + tp2.Position.Y) / 2 * newViewportWidth / AssociatedObject.ActualWidth
                    };

                    AssociatedObject.UseSprings = false;
                    AssociatedObject.ViewportWidth = newViewportWidth;
                    AssociatedObject.ViewportOrigin = newViewportOrigin;
                    lastElementPoint1 = tp1.Position;
                    lastElementPoint2 = tp2.Position;
                }

            }
        }

        private double Distance(Point p0, Point p1)
        {
            return Math.Sqrt(Math.Pow(p0.X - p1.X, 2) + Math.Pow(p0.Y - p1.Y, 2));
        }

        private bool InError(Point p0, Point p1)
        {
            return Math.Abs(p0.X - p1.X) < diff && Math.Abs(p0.Y - p1.Y) < diff;
        }
        

        
    }
}
